{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "56db45b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| default_exp xtend"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80f4f9d8",
   "metadata": {},
   "source": [
    "# Component extensions\n",
    "> Simple extensions to standard HTML components, such as adding sensible defaults"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8e2d405b",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "from dataclasses import dataclass, asdict\n",
    "from typing import Any\n",
    "\n",
    "from fastcore.utils import *\n",
    "from fastcore.xtras import partial_format\n",
    "from fastcore.xml import *\n",
    "from fastcore.meta import use_kwargs, delegates\n",
    "from fasthtml.components import *\n",
    "\n",
    "try: from IPython import display\n",
    "except ImportError: display=None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0319fa2",
   "metadata": {},
   "outputs": [],
   "source": [
    "from pprint import pprint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "254f4744",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def A(*c, hx_get=None, target_id=None, hx_swap=None, href='#', **kwargs)->FT:\n",
    "    \"An A tag; `href` defaults to '#' for more concise use with HTMX\"\n",
    "    return ft_hx('a', *c, href=href, hx_get=hx_get, target_id=target_id, hx_swap=hx_swap, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "53efcd61",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```html\n",
       "<a href=\"#\" ht-get=\"/get\" hx-target=\"#id\">text</a>\n",
       "\n",
       "```"
      ],
      "text/plain": [
       "a(('text',),{'href': '#', 'ht-get': '/get', 'hx-target': '#id'})"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A('text', ht_get='/get', target_id='id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "465f147c",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def Form(*c, enctype=\"multipart/form-data\", **kwargs)->FT:\n",
    "    \"A Form tag; identical to plain `ft_hx` version except default `enctype='multipart/form-data'`\"\n",
    "    return ft_hx('form', *c, enctype=enctype, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f0bd0fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def AX(txt, hx_get=None, target_id=None, hx_swap=None, href='#', **kwargs)->FT:\n",
    "    \"An A tag with just one text child, allowing hx_get, target_id, and hx_swap to be positional params\"\n",
    "    return ft_hx('a', txt, href=href, hx_get=hx_get, target_id=target_id, hx_swap=hx_swap, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2531e3fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```html\n",
       "<a href=\"#\" hx-get=\"/get\" hx-target=\"#id\">text</a>\n",
       "\n",
       "```"
      ],
      "text/plain": [
       "a(('text',),{'href': '#', 'hx-get': '/get', 'hx-target': '#id'})"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "AX('text', '/get', 'id')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6aaebe19",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def Hidden(value:Any=\"\", id:Any=None, **kwargs)->FT:\n",
    "    \"An Input of type 'hidden'\"\n",
    "    return Input(type=\"hidden\", value=value, id=id, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "88863fe3",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def CheckboxX(checked:bool=False, label=None, value=\"1\", id=None, name=None, **kwargs)->FT:\n",
    "    \"A Checkbox optionally inside a Label, preceded by a `Hidden` with matching name\"\n",
    "    if id and not name: name=id\n",
    "    if not checked: checked=None\n",
    "    res = Input(type=\"checkbox\", id=id, name=name, checked=checked, value=value, **kwargs)\n",
    "    if label: res = Label(res, label)\n",
    "    return Hidden(name=name, skip=True, value=\"\"), res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d9e3af6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<input type=\"hidden\" value=\"\" skip>\n",
       "\n",
       "<label>\n",
       "  <input type=\"checkbox\" checked value=\"1\">\n",
       "Check me out!\n",
       "</label>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "show(CheckboxX(True, 'Check me out!'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "854dc037",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_html, keep=True)\n",
    "def Script(code:str=\"\", **kwargs)->FT:\n",
    "    \"A Script tag that doesn't escape its code\"\n",
    "    return ft_html('script', NotStr(code), **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d08fe828",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_html, keep=True)\n",
    "def Style(*c, **kwargs)->FT:\n",
    "    \"A Style tag that doesn't escape its code\"\n",
    "    return ft_html('style', map(NotStr,c), **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c77a0e88",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def double_braces(s):\n",
    "    \"Convert single braces to double braces if next to special chars or newline\"\n",
    "    s = re.sub(r'{(?=[\\s:;\\'\"]|$)', '{{', s)\n",
    "    return re.sub(r'(^|[\\s:;\\'\"])}', r'\\1}}', s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9ca90ee1",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def undouble_braces(s):\n",
    "    \"Convert double braces to single braces if next to special chars or newline\"\n",
    "    s = re.sub(r'\\{\\{(?=[\\s:;\\'\"]|$)', '{', s)\n",
    "    return re.sub(r'(^|[\\s:;\\'\"])\\}\\}', r'\\1}', s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "231b7004",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def loose_format(s, **kw):\n",
    "    \"String format `s` using `kw`, without being strict about braces outside of template params\"\n",
    "    if not kw: return s\n",
    "    return undouble_braces(partial_format(double_braces(s), **kw)[0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "29ceb26a",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def ScriptX(fname, src=None, nomodule=None, type=None, _async=None, defer=None,\n",
    "            charset=None, crossorigin=None, integrity=None, **kw):\n",
    "    \"A `script` element with contents read from `fname`\"\n",
    "    s = loose_format(Path(fname).read_text(), **kw)\n",
    "    return Script(s, src=src, nomodule=nomodule, type=type, _async=_async, defer=defer,\n",
    "                  charset=charset, crossorigin=crossorigin, integrity=integrity)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1e72dac9",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def replace_css_vars(css, pre='tpl', **kwargs):\n",
    "    \"Replace `var(--)` CSS variables with `kwargs` if name prefix matches `pre`\"\n",
    "    if not kwargs: return css\n",
    "    def replace_var(m):\n",
    "        var_name = m.group(1).replace('-', '_')\n",
    "        return kwargs.get(var_name, m.group(0))\n",
    "    return re.sub(fr'var\\(--{pre}-([\\w-]+)\\)', replace_var, css)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bcbff42",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def StyleX(fname, **kw):\n",
    "    \"A `style` element with contents read from `fname` and variables replaced from `kw`\"\n",
    "    s = Path(fname).read_text()\n",
    "    attrs = ['type', 'media', 'scoped', 'title', 'nonce', 'integrity', 'crossorigin']\n",
    "    sty_kw = {k:kw.pop(k) for k in attrs if k in kw}\n",
    "    return Style(replace_css_vars(s, **kw), **sty_kw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f87a71fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def On(code:str, event:str='click', sel:str='', me=True):\n",
    "    \"An async surreal.js script block event handler for `event` on selector `sel`\"\n",
    "    func = 'me' if me else 'any'\n",
    "    if sel: sel=f'\"{sel}\"'\n",
    "    return Script(f'{func}({sel}).on(\"{event}\", async ev=>{{\\nlet e = me(ev);\\n{code}\\n}});\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17895311",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def Prev(code:str, event:str='click'):\n",
    "    \"An async surreal.js script block event handler for `event` on previous sibling\"\n",
    "    return On(code, event=event, sel='-')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "917c5ad4",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def Now(code:str, sel:str=''):\n",
    "    \"An async surreal.js script block on selector `me(sel)`\"\n",
    "    if sel: sel=f'\"{sel}\"'\n",
    "    return Script(f'(async (ee = me({sel})) => {{\\nlet e = me(ee);\\n{code}\\n}})()\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da93a394",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def AnyNow(sel:str, code:str):\n",
    "    \"An async surreal.js script block on selector `any(sel)`\"\n",
    "    return Script(f'(async (e = any(\"{sel}\")) => {{\\n{code}\\n}})()\\n')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24d31af2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def run_js(js, id=None, **kw):\n",
    "    \"Run `js` script, auto-generating `id` based on name of caller if needed, and js-escaping any `kw` params\"\n",
    "    if not id: id = sys._getframe(1).f_code.co_name\n",
    "    kw = {k:dumps(v) for k,v in kw.items()}\n",
    "    return Script(js.format(**kw), id=id, hx_swap_oob='true')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "365f57a8",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def HtmxOn(eventname:str, code:str):\n",
    "    return Script(f'''document.addEventListener('DOMContentLoaded', function() {{\n",
    "document.body.addEventListener(\"htmx:{eventname}\", function(event) {{ {code} }})\n",
    "}})''')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5e6d3c46",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "@delegates(ft_hx, keep=True)\n",
    "def Titled(title:str=\"FastHTML app\", *args, cls=\"container\", **kwargs)->FT:\n",
    "    \"An HTML partial containing a `Title`, and `H1`, and any provided children\"\n",
    "    return Title(title), Main(H1(title), *args, cls=cls, **kwargs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ba5cf4e",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def Socials(title, site_name, description, image, url=None, w=1200, h=630, twitter_site=None, creator=None, card='summary'):\n",
    "    \"OG and Twitter social card headers\"\n",
    "    if not url: url=site_name\n",
    "    if not url.startswith('http'): url = f'https://{url}'\n",
    "    if not image.startswith('http'): image = f'{url}{image}'\n",
    "    res = [Meta(property='og:image', content=image),\n",
    "        Meta(property='og:site_name', content=site_name),\n",
    "        Meta(property='og:image:type', content='image/png'),\n",
    "        Meta(property='og:image:width', content=w),\n",
    "        Meta(property='og:image:height', content=h),\n",
    "        Meta(property='og:type', content='website'),\n",
    "        Meta(property='og:url', content=url),\n",
    "        Meta(property='og:title', content=title),\n",
    "        Meta(property='og:description', content=description),\n",
    "        Meta(name='twitter:image', content=image),\n",
    "        Meta(name='twitter:card', content=card),\n",
    "        Meta(name='twitter:title', content=title),\n",
    "        Meta(name='twitter:description', content=description)]\n",
    "    if twitter_site is not None: res.append(Meta(name='twitter:site',    content=twitter_site))\n",
    "    if creator      is not None: res.append(Meta(name='twitter:creator', content=creator))\n",
    "    return tuple(res)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28c9496d",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def Favicon(light_icon, dark_icon):\n",
    "    \"Light and dark favicon headers\"\n",
    "    return (Link(rel='icon', type='image/x-ico', href=light_icon, media='(prefers-color-scheme: light)'),\n",
    "            Link(rel='icon', type='image/x-ico', href=dark_icon, media='(prefers-color-scheme: dark)'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "500110e5",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def jsd(org, repo, root, path, prov='gh', typ='script', ver=None, esm=False, **kwargs)->FT:\n",
    "    \"jsdelivr `Script` or CSS `Link` tag, or URL\"\n",
    "    ver = '@'+ver if ver else ''\n",
    "    s = f'https://cdn.jsdelivr.net/{prov}/{org}/{repo}{ver}/{root}/{path}'\n",
    "    if esm: s += '/+esm'\n",
    "    return Script(src=s, **kwargs) if typ=='script' else Link(rel='stylesheet', href=s, **kwargs) if typ=='css' else s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50444181",
   "metadata": {},
   "outputs": [],
   "source": [
    "#| export\n",
    "def clear(id): return Div(hx_swap_oob='innerHTML', id=id)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "474e14b4",
   "metadata": {},
   "source": [
    "# Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d211e8e2",
   "metadata": {},
   "outputs": [],
   "source": [
    "#|hide\n",
    "import nbdev; nbdev.nbdev_export()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0a942593",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "python3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
